package com.wanxi.spring.cloud.security.service.impl;


import com.wanxi.spring.cloud.commons.api.CommonResult;
import com.wanxi.spring.cloud.security.commons.JwtTokenUtil;
import com.wanxi.spring.cloud.security.mapper.RoleMapper;
import com.wanxi.spring.cloud.security.mapper.UserMapper;
import com.wanxi.spring.cloud.security.model.Permission;
import com.wanxi.spring.cloud.security.model.Role;
import com.wanxi.spring.cloud.security.model.User;
import com.wanxi.spring.cloud.security.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.regex.Pattern;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 蒋帆
 * @since 2020-12-21
 */
@Service("userDetailsService")
@CacheConfig(cacheNames = "springcloud::user")
public class UserServiceImpl implements UserService, UserDetailsService {

    private Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    @Value("${jwt.tokenHead}")
    private String tokenHead;//bearer

    @Autowired(required = false)
    UserMapper userMapper;
    @Autowired
    PasswordEncoder passwordEncoder;
    @Autowired
    JwtTokenUtil jwtTokenUtil;
    @Autowired
    RedisServiceImpl redisService;
    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Autowired(required = false)
    RoleMapper roleMapper;

    @Override
    public CommonResult getUserByName(String username) {//根据用户名获取用户
        User user = null;//判断缓存是否有该用户
        if (!redisService.hasKey("springcloud::user::" + username))
            user = userMapper.getUserByName(username);//如果没有，访问数据库
        else//如果有，则直接从缓存中拿到该值
            user = (User) redisService.get("springcloud::user::" + username);
        return CommonResult.success(user);
    }

    /**
     * 用户登录
     *
     * @param user
     * @return
     */
    @Override
    public CommonResult login(User user) {
        String token = null;
        HashMap<String, Object> data = new HashMap<>();
        User usr = null;
        //如果没有该用户冻结标识,则允许进行登录操作
        if (!redisService.hasKey("springcloud::user::" + user.getCode() + "::frozen")) {
            try {
                //判断Redis是否有该用户信息
                if (!redisService.hasKey("springcloud::user::" + user.getCode()))
                    usr = (User) getUserByName(user.getCode()).getData();//没有则从数据库取值

                else//Redis中有该键值则从Redis中取值
                    usr = (User) redisService.get("springcloud::user::" + user.getCode());
                //判断输入的密码是否与数据库相等
                boolean matches = passwordEncoder.matches(user.getPassword(), usr.getPassword());
                if (matches) {//如果相等则生成token
                    //获取用户权限
                    List<Permission> permissionList = userMapper.getPermissionsByUserId(usr.getId());
                    HashSet<Permission> permissions = new HashSet<>(permissionList);
                    usr.setAuthorities(permissions);
                    token = jwtTokenUtil.generateToken(usr);
                } else
                    token = null;
            } catch (Exception e) {
                e.printStackTrace();
                return CommonResult.validateFailed("用户名或密码错误");
            }
            //没有token则记录用户错误次数
            if (!StringUtils.hasLength(token)) {
                return recordErrorToRedis(user.getCode());
            }
            //封装用户头像
            //如果用户头像值不为空
            if (usr.getIcon() != null && usr.getIcon() != "") {
                //判断是否是base64格式
                //先去掉逗号以前的数据头
                int index = usr.getIcon().indexOf(",");//获得逗号的索引值
                if (index == -1)//如果没找到该索引
                    usr.setIcon("http://t.cn/RCzsdCq");//则直接设置默认头像
                else {//新建base64编码正则表达式
                    String base64Pattern = "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$";
                    boolean match = Pattern.matches(base64Pattern, usr.getIcon().substring(index + 1));//判断是否符合正则表达式
                    if (!match)//如果不匹配，则设置为默认头像
                        usr.setIcon("http://t.cn/RCzsdCq");
                }
            } else
                usr.setIcon("http://t.cn/RCzsdCq");
            //获取用户角色
            Role role = roleMapper.getRoleByUserId(usr.getId());
            if (role != null) {
                usr.setRole(role.getId());
                usr.setRoleName(role.getName());
            }
//            打印登录日志
//            logging(user.getCode());
            //将token传入前台
            data.put("userInfo", usr);
            data.put("tokenHead", tokenHead);
            data.put("access_token", token);
            //清除用户的密码错误次数记录
            clearRedisErrorCode(user.getCode());
            //将用户登录信息存入缓存
            redisService.set("springcloud::user::" + user.getCode(), usr, 86400);
            return CommonResult.success(data, "登录成功！");
        } else {//如果有该用户冻结标识，则直接反馈冻结信息给用户
            long expire = redisService.getExpire("springcloud::user::" + user.getCode() + "::frozen");
            long frozenHours = expire / 3600;
            long frozenMinutes = expire % 3600 / 60;
            long frozenSeconds = expire % 3600 % 60;
            return CommonResult.validateFailed("该账号已被冻结，请" + frozenHours + "小时" + frozenMinutes + "分" + frozenSeconds + "秒后再试");
        }
    }

    @Override
    public CommonResult sendToken(String authHeader) {
        // 判断 authHeader  不为空  并且以 bearer 开头
        if (authHeader != null) {
            boolean b1 = org.springframework.util.StringUtils.startsWithIgnoreCase(authHeader, this.tokenHead);
            if (b1) {
                //截取 bearer 后面的字符串  并且 两端去空格（获取token）
                String authToken = authHeader.substring(this.tokenHead.length()).trim();// The part after "Bearer "

                String username = jwtTokenUtil.getUserNameFromToken(authToken);
                if (username == null) {
                    return CommonResult.failed("token已失效");
                } else {
                    LOGGER.info("checking username:{}", username);
                    //验证权限
                    // 用户名不为空  并且SecurityContextHolder.getContext()  存储 权限的容器中没有相关权限则继续
                    boolean b = SecurityContextHolder.getContext().getAuthentication() == null;
                    if (b) {
                        //从数据库读取用户信息
                        UserDetails userDetails = loadUserByUsername(username);
                        //校验token
                        if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
//                            WebAuthenticationDetails details = new WebAuthenticationDetailsSource().buildDetails(request);
                            //设置用户ip
//                            authentication.setDetails(details);
                            LOGGER.info("authenticated user:{}", username);
                            //存入本线程的安全容器   在访问接口拿到返回值后 要去主动清除 权限，避免干扰其他的线程
                            SecurityContextHolder.getContext().setAuthentication(authentication);
                            return CommonResult.success("token验证成功");
                        } else
                            return CommonResult.failed("token验证失败");
                    } else
                        return CommonResult.success("用户已被授权");
                }
            } else
                return CommonResult.validateFailed("token无效");
        } else {
            return CommonResult.forbidden("无token");
        }
    }

    @Override
    public void code(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //        创建空白图片
        BufferedImage image = new BufferedImage(100, 30, BufferedImage.TYPE_INT_RGB);
        //获取图片画笔
        Graphics g = image.getGraphics();
        Random random = new Random();//设置随机数
        //设置画笔颜色
        g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
        //绘制矩形背景
        g.fillRect(0, 0, 100, 30);
        //调用自定义的方法获取长度为5的字母数字组合的字符串
        String number = getNumber(4);
        System.out.println(number);
        req.getSession().setAttribute("code", number);
        g.setColor(new Color(0, 0, 0));
        g.setFont(new Font("宋体", Font.BOLD, 24));
        //设置颜色字体后，绘制字符串
        g.drawString(number, 4, 25);
        //绘制18条干扰线
        for (int i = 0; i < 18; i++) {
            g.setColor(
                    new Color(
                            random.nextInt(255),
                            random.nextInt(255),
                            random.nextInt(255),
                            random.nextInt(255)
                    )
            );
            g.drawLine(
                    random.nextInt(100),
                    random.nextInt(30),
                    random.nextInt(100),
                    random.nextInt(30)
                    //直线参数（a,b,x,y）a，b代表起点，x，y代表终点
            );
        }
        resp.setContentType("image/ jpeg");
        OutputStream ops = resp.getOutputStream();
        ImageIO.write(image, "jpeg", ops);
        ops.close();
    }

    @Override
    public CommonResult validateCode(String code, HttpServletRequest req) {
        String imgcode = (String) req.getSession().getAttribute("code");
        if (imgcode.equalsIgnoreCase(code))
            return CommonResult.success(true);
        return CommonResult.validateFailed("验证码输入错误");
    }

    /**
     * 字符串的拼接
     *
     * @param size
     * @return
     */
    private String getNumber(int size) {
        //因为英文字母Oo和数字0，英文字母l和数字1过于相似不好辨认，因此生成验证码时去除掉这几个字符
        String str = "ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
        StringBuilder number = new StringBuilder();
        Random r = new Random();
        for (int i = 0; i < size; i++) {
            number.append(str.charAt(r.nextInt(str.length())));
        }
        return number.toString();
    }

    /**
     * 记录用户输入登录信息错误次数
     */
    public CommonResult recordErrorToRedis(String username) {
        //判断用户是否有输入错误次数
        if (redisService.hasKey("springcloud::user::" + username + "::errorCount")) {
            //如果有，则获取输入错误的次数
            int count = (int) redisService.get("springcloud::user::" + username + "::errorCount");
            count++;//将输入错误次数加1
            if (count >= 3) {//如果加1后的值大于或等于3，则生成一个冻结标识到Redis，并设定失效时间为12小时
                redisService.set("springcloud::user::" + username + "::frozen", true, 43200);
                redisService.expire("springcloud::user::" + username + "::frozen", 43200);
                redisService.del("springcloud::user::" + username + "::errorCount");
                return CommonResult.validateFailed("您已输入错误三次，账号已被冻结，请12小时后再试");
            } else {
                redisService.expireAt("springcloud::user::" + username + "::errorCount", count, 10800);
            }
        } else {//如果之前没有，则生成一个错误次数缓存到Redis，并设定失效时间为3小时内
            redisService.set("springcloud::user::" + username + "::errorCount", 1, 10800);
            redisService.expire("springcloud:user::" + username + "::errorCount", 10800);
//                redisService.expireAt("springcloud:user:" + username + ":errorCount", 1, 10800);
        }
        return CommonResult.validateFailed("用户名或密码错误");
    }

    /**
     * 清除错误计数
     */
    private void clearRedisErrorCode(String username) {
        redisService.del("springcloud::user::" + username + "::errorCount", "springcloud::user::" + username + "::frozen");
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.getUserByName(username);
        List<Permission> permissionList = userMapper.getPermissionsByUserId(user.getId());
        HashSet<Permission> permissions = new HashSet<>(permissionList);
        user.setAuthorities(permissions);
        return user;
    }
}
